<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8" />
    <title>DPlayer 加密 DRM 播放器</title>
    <script src="/js/hls.js"></script>
    <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/dplayer/1.26.0/DPlayer.min.js"></script>
    <style>
      body {
        font-family: Arial, sans-serif;
        padding: 20px;
        max-width: 1200px;
        margin: 0 auto;
        background-color: #f5f5f5;
      }
      .container {
        background-color: white;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      }
      h1 {
        color: #333;
        border-bottom: 2px solid #eee;
        padding-bottom: 10px;
      }
      #dplayer {
        width: 100%;
        max-width: 800px;
        height: 450px;
        margin: 20px 0;
        border-radius: 8px;
        overflow: hidden;
      }
      button {
        background-color: #4caf50;
        border: none;
        color: white;
        padding: 10px 15px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 14px;
        margin: 4px 8px 4px 0;
        cursor: pointer;
        border-radius: 4px;
        transition: background-color 0.3s;
      }
      button:hover {
        background-color: #45a049;
      }
      button:disabled {
        background-color: #cccccc;
        cursor: not-allowed;
      }
      .status {
        margin: 15px 0;
        padding: 10px;
        background-color: #f0f0f0;
        border-radius: 4px;
        font-size: 14px;
      }
      .error {
        color: #d32f2f;
        background-color: #ffebee;
        padding: 10px;
        border-radius: 4px;
        margin: 10px 0;
        display: none;
      }
      .login-form {
        margin: 20px 0;
        padding: 15px;
        background-color: #f9f9f9;
        border-radius: 4px;
        border: 1px solid #ddd;
      }
      .form-group {
        margin-bottom: 10px;
      }
      .form-group label {
        display: block;
        margin-bottom: 5px;
        font-weight: bold;
      }
      .form-group input {
        width: 100%;
        padding: 8px;
        border: 1px solid #ddd;
        border-radius: 4px;
        box-sizing: border-box;
      }
      .auth-status {
        padding: 10px;
        margin: 10px 0;
        border-radius: 4px;
      }
      .auth-status.logged-in {
        background-color: #e8f5e9;
        color: #2e7d32;
      }
      .auth-status.logged-out {
        background-color: #fafafa;
        color: #757575;
      }
      .hidden {
        display: none;
      }
      .token-info {
        font-size: 12px;
        color: #666;
        margin-top: 10px;
        word-break: break-all;
      }
      .controls {
        margin: 15px 0;
      }
      .version {
        font-size: 12px;
        color: #666;
        margin-top: 5px;
      }
      .feature-list {
        background-color: #f8f9fa;
        padding: 15px;
        border-radius: 4px;
        margin: 15px 0;
      }
      .feature-list ul {
        margin: 0;
        padding-left: 20px;
      }
      .feature-list li {
        margin-bottom: 5px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>DPlayer 加密 DRM 播放器</h1>
      <p>
        这个播放器演示了如何使用 DPlayer 播放使用 AES-128 加密的 HLS 视频内容，
        并通过类似DRM的授权系统进行密钥管理。
      </p>
      <div class="version">DPlayer 版本: 1.26.0 | HLS.js 版本: 1.1.5</div>

      <div class="feature-list">
        <strong>功能特性:</strong>
        <ul>
          <li>🔐 AES-128 加密视频播放</li>
          <li>🎭 用户认证和授权管理</li>
          <li>🎬 DPlayer 美观的播放界面</li>
          <li>📱 响应式设计，支持移动设备</li>
          <li>🔄 自动错误恢复机制</li>
          <li>📊 实时播放状态监控</li>
        </ul>
      </div>

      <!-- 认证区域 -->
      <div id="auth-section">
        <div id="login-form" class="login-form">
          <h3>用户登录</h3>
          <div class="form-group">
            <label for="username">用户名</label>
            <input type="text" id="username" value="admin" />
          </div>
          <div class="form-group">
            <label for="password">密码</label>
            <input type="password" id="password" value="admin" />
          </div>
          <button onclick="login()">登录</button>
        </div>

        <div id="auth-status" class="auth-status logged-out">
          <span id="auth-message">未登录</span>
          <button id="logout-btn" onclick="logout()" style="display: none">登出</button>
        </div>

        <div id="token-info" class="token-info hidden"></div>
      </div>

      <div class="controls">
        <button id="play-btn" onclick="initDPlayer()" disabled>初始化加密播放器</button>
        <button id="destroy-btn" onclick="destroyPlayer()" disabled>销毁播放器</button>
      </div>

      <div id="status" class="status">状态: 等待登录</div>
      <div id="error" class="error"></div>

      <!-- DPlayer 容器 -->
      <div id="dplayer"></div>
    </div>

    <script>
      // DRM系统配置
      const DRM_CONFIG = {
        LICENSE_SERVER: "http://localhost:3000",
        AUTH_ENDPOINT: "/drm/auth/token",
        KEY_ENDPOINT: "/drm/key",
        LICENSE_INFO_ENDPOINT: "/drm/info"
      }

      // DOM元素
      const statusElement = document.getElementById("status")
      const errorElement = document.getElementById("error")
      const playButton = document.getElementById("play-btn")
      const destroyButton = document.getElementById("destroy-btn")
      const authStatusElement = document.getElementById("auth-status")
      const authMessageElement = document.getElementById("auth-message")
      const logoutButton = document.getElementById("logout-btn")
      const loginForm = document.getElementById("login-form")
      const tokenInfoElement = document.getElementById("token-info")

      // 状态变量
      let dp = null
      let authToken = null
      let tokenExpiry = null

      // 页面加载时初始化
      window.onload = function () {
        logout()
        updateStatus("页面已加载，请先登录")
      }

      // 用户登录
      function login() {
        const username = document.getElementById("username").value
        const password = document.getElementById("password").value

        if (!username || !password) {
          showError("请输入用户名和密码")
          return
        }

        updateStatus("正在验证用户凭据...")

        // 发送登录请求
        fetch(`${DRM_CONFIG.LICENSE_SERVER}${DRM_CONFIG.AUTH_ENDPOINT}`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json"
          },
          body: JSON.stringify({ username, password })
        })
          .then(response => {
            if (!response.ok) {
              throw new Error(`认证失败: ${response.status}`)
            }
            return response.json()
          })
          .then(data => {
            // 保存令牌
            authToken = data.token

            // 计算过期时间
            const expiryTime = Date.now() + data.expires_in * 1000
            tokenExpiry = new Date(expiryTime)

            // 保存到本地存储
            localStorage.setItem("drm_token", authToken)
            localStorage.setItem("drm_token_expiry", expiryTime.toString())

            updateStatus("登录成功，可以初始化播放器")
            updateAuthStatus(true)

            // 显示令牌信息
            const tokenPreview = `${authToken.substring(0, 20)}...`
            tokenInfoElement.textContent = `令牌: ${tokenPreview} (过期时间: ${tokenExpiry.toLocaleString()})`
            tokenInfoElement.classList.remove("hidden")
          })
          .catch(error => {
            showError(`登录失败: ${error.message}`)
            updateAuthStatus(false)
          })
      }

      // 用户登出
      function logout() {
        authToken = null
        tokenExpiry = null

        // 清除本地存储
        localStorage.removeItem("drm_token")
        localStorage.removeItem("drm_token_expiry")

        updateAuthStatus(false)
        updateStatus("已登出")

        // 销毁播放器
        destroyPlayer()
      }

      // 更新认证状态UI
      function updateAuthStatus(isLoggedIn) {
        if (isLoggedIn) {
          authStatusElement.className = "auth-status logged-in"
          authMessageElement.textContent = "已登录"
          logoutButton.style.display = "inline-block"
          loginForm.style.display = "none"
          playButton.disabled = false
        } else {
          authStatusElement.className = "auth-status logged-out"
          authMessageElement.textContent = "未登录"
          logoutButton.style.display = "none"
          loginForm.style.display = "block"
          playButton.disabled = true
          destroyButton.disabled = true
          tokenInfoElement.classList.add("hidden")
        }
      }

      // 初始化 DPlayer
      function initDPlayer() {
        // 检查令牌是否有效
        if (!authToken) {
          showError("请先登录")
          return
        }

        clearError()
        updateStatus("正在初始化 DPlayer...")

        // 销毁现有播放器
        destroyPlayer()

        try {
          // 创建自定义 HLS 配置
          const hlsConfig = {
            debug: false,
            maxBufferLength: 30,
            maxMaxBufferLength: 600,
            maxBufferSize: 60 * 1000 * 1000,
            maxBufferHole: 0.5,
            lowLatencyMode: false,
            highBufferWatchdogPeriod: 3,
            nudgeOffset: 0.2,
            nudgeMaxRetry: 5,
            startFragPrefetch: true,
            fragLoadingMaxRetry: 6,
            manifestLoadingMaxRetry: 6,
            levelLoadingMaxRetry: 6,
            xhrSetup: function (xhr, url) {
              // 为密钥请求添加授权头
              if (url.includes(DRM_CONFIG.KEY_ENDPOINT)) {
                if (authToken) {
                  xhr.setRequestHeader("Authorization", `Bearer ${authToken}`)
                  console.log("已添加授权头到密钥请求:", url)
                } else {
                  console.warn("没有授权令牌，密钥请求可能会失败")
                }
              }
            }
          }

          // 初始化 DPlayer
          dp = new DPlayer({
            container: document.getElementById('dplayer'),
            logo: '/favicon.ico',
            lang: 'zh-cn',
            theme: '#b7daff',
            screenshot: true,
            hotkey: true,
            preload: 'metadata',
            volume: 0.7,
            mutex: true,
            video: {
              url: '/drm/index.m3u8',
              type: 'hls',
              customType: {
                hls: function(video, player) {
                  const hls = new Hls(hlsConfig)

                  // 设置 HLS 事件监听
                  setupHlsEvents(hls, player)

                  hls.loadSource(video.src)
                  hls.attachMedia(video)

                  // 保存 hls 实例到 player 对象
                  player.hls = hls
                }
              }
            },
            highlight: [
              {
                time: 10,
                text: '加密内容开始播放'
              },
              {
                time: 30,
                text: 'DRM 授权验证成功'
              }
            ]
          })

          // DPlayer 事件监听
          setupDPlayerEvents()

          playButton.disabled = true
          destroyButton.disabled = false
          updateStatus("DPlayer 初始化成功，正在加载加密视频...")

        } catch (error) {
          console.error("初始化 DPlayer 失败:", error)
          showError(`初始化播放器失败: ${error.message}`)
        }
      }

      // 设置 HLS 事件监听
      function setupHlsEvents(hls, player) {
        // 监听错误
        hls.on(Hls.Events.ERROR, function (event, data) {
          console.error("HLS错误:", data)

          if (data.fatal) {
            switch (data.type) {
              case Hls.ErrorTypes.NETWORK_ERROR:
                if (data.details === Hls.ErrorDetails.KEY_LOAD_ERROR) {
                  showError("密钥加载失败，可能是授权问题。请确保您已登录并有权访问此内容。")
                  player.notice("密钥加载失败，请检查授权", 3000)
                } else {
                  showError(`网络错误，尝试恢复... 详情: ${data.details}`)
                  player.notice("网络错误，正在尝试恢复...", 2000)
                  hls.startLoad()
                }
                break
              case Hls.ErrorTypes.MEDIA_ERROR:
                showError(`媒体错误，尝试恢复... 详情: ${data.details}`)
                player.notice("媒体错误，正在尝试恢复...", 2000)
                hls.recoverMediaError()
                break
              default:
                showError(`无法恢复的错误: ${data.details}`)
                player.notice("播放器遇到无法恢复的错误", 3000)
                break
            }
          } else {
            console.warn("非致命错误:", data)
            if (data.details === Hls.ErrorDetails.BUFFER_STALLED_ERROR) {
              updateStatus("正在处理缓冲停滞...")
              player.notice("缓冲中，请稍候...", 1000)
            }
          }
        })

        hls.on(Hls.Events.MANIFEST_PARSED, function () {
          updateStatus("视频清单已解析，准备播放")
          console.log("可用质量级别:", hls.levels.length)
        })

        hls.on(Hls.Events.KEY_LOADING, function (event, data) {
          updateStatus("正在加载解密密钥...")
          console.log("密钥加载中:", data.frag.decryptdata.uri)
        })

        hls.on(Hls.Events.KEY_LOADED, function (event, data) {
          updateStatus("解密密钥已加载，视频可以播放")
          console.log("密钥已加载")
        })

        hls.on(Hls.Events.FRAG_LOADING, function (event, data) {
          updateStatus(`正在加载视频片段 #${data.frag.sn}...`)
        })

        hls.on(Hls.Events.FRAG_LOADED, function (event, data) {
          updateStatus(`视频片段 #${data.frag.sn} 已加载`)
        })

        hls.on(Hls.Events.LEVEL_SWITCHED, function (event, data) {
          const level = hls.levels[data.level]
          if (level) {
            updateStatus(`切换到质量: ${level.width}x${level.height} (${Math.round(level.bitrate/1000)} kbps)`)
            player.notice(`质量: ${level.width}x${level.height}`, 1000)
          }
        })
      }

      // 设置 DPlayer 事件监听
      function setupDPlayerEvents() {
        dp.on('loadstart', () => {
          updateStatus("开始加载视频...")
        })

        dp.on('loadedmetadata', () => {
          updateStatus("视频元数据已加载")
        })

        dp.on('canplay', () => {
          updateStatus("视频可以开始播放")
        })

        dp.on('play', () => {
          updateStatus("视频开始播放")
        })

        dp.on('pause', () => {
          updateStatus("视频已暂停")
        })

        dp.on('ended', () => {
          updateStatus("视频播放结束")
        })

        dp.on('error', (error) => {
          console.error("DPlayer 错误:", error)
          showError(`播放器错误: ${error.message || '未知错误'}`)
        })

        dp.on('waiting', () => {
          updateStatus("视频缓冲中...")
        })

        dp.on('playing', () => {
          updateStatus("视频正在播放")
        })
      }

      // 销毁播放器
      function destroyPlayer() {
        if (dp) {
          try {
            // 如果有 HLS 实例，先销毁它
            if (dp.hls) {
              dp.hls.destroy()
              dp.hls = null
            }

            dp.destroy()
            dp = null

            updateStatus("播放器已销毁")
            playButton.disabled = false
            destroyButton.disabled = true
          } catch (error) {
            console.error("销毁播放器时出错:", error)
            showError(`销毁播放器失败: ${error.message}`)
          }
        }
      }

      // 更新状态显示
      function updateStatus(message) {
        statusElement.textContent = "状态: " + message
        console.log("状态更新:", message)
      }

      // 显示错误
      function showError(message) {
        errorElement.textContent = "错误: " + message
        errorElement.style.display = "block"
        console.error("显示错误:", message)
      }

      // 清除错误
      function clearError() {
        errorElement.textContent = ""
        errorElement.style.display = "none"
      }
    </script>
  </body>
</html>
